package net.pimpas.web.tests.auth.login;

import java.security.GeneralSecurityException;
import java.util.Locale;
import java.util.Map;

import net.pimpas.core.session.FrameworkSession;
import net.pimpas.framework.PimpasFramework;
import net.pimpas.logging.serviceprovider.LoggingServiceProvider;
import net.pimpas.logging.serviceprovider.LoggingServiceProviderFactoryImpl;
import net.pimpas.operation.persistence.serviceprovider.OperationPersistenceServiceProvider;
import net.pimpas.operation.persistence.serviceprovider.OperationPersistenceServiceProviderFactoryImpl;
import net.pimpas.resourcebundle.exception.MessageKeyNotFoundException;
import net.pimpas.resourcebundle.exception.ResourceBundleNotFoundException;
import net.pimpas.resourcebundle.serviceprovider.ResourceBundleServiceProvider;
import net.pimpas.resourcebundle.serviceprovider.ResourceBundleServiceProviderFactoryImpl;
import net.pimpas.resourcebundle.serviceprovider.ResourceBundleServiceProviderType;
import net.pimpas.security.jaas.serviceprovider.auth.login.JAASLoginModuleAbstract;
import net.pimpas.security.serviceprovider.SecurityServiceProvider;
import net.pimpas.security.serviceprovider.SecurityServiceProviderFactoryImpl;
import net.pimpas.security.serviceprovider.auth.Subject;
import net.pimpas.security.serviceprovider.auth.login.LoginException;
import net.pimpas.security.serviceprovider.auth.login.LoginUserAlreadyLoggedException;
import net.pimpas.security.serviceprovider.auth.login.LoginUserNotLoggedInException;
import net.pimpas.security.serviceprovider.auth.login.callback.Callback;
import net.pimpas.security.serviceprovider.auth.login.callback.CallbackHandler;
import net.pimpas.security.serviceprovider.auth.login.principal.Principal;
import net.pimpas.web.tests.entitys.user.User;

public class UserLoginModule extends JAASLoginModuleAbstract {
	
	private boolean debugMode;
	private Subject subject;
	private CallbackHandler handler;
	private User user;
	private static final Integer			OPERATION_LOGIN		= 0;
	private static final Integer			OPERATION_LOGOFF	= 1;
	private Integer operation;
	
	private static final LoggingServiceProvider logger = 
		LoggingServiceProviderFactoryImpl.getLoggingServiceProvider(UserLoginModule.class);
	private static final ResourceBundleServiceProvider bundle = 
		ResourceBundleServiceProviderFactoryImpl.getResourceBundleServiceProvider();
	
	public UserLoginModule() {
		this.setDebugMode(false);
		this.setOperation(null);
	}

	public void init(CallbackHandler callbackHandler, Subject subject, Map<String, ?> sharedState, Map<String, ?> options) {
		this.setDebugMode(options.get("debug").equals("true"));
		this.setSubject(subject);
		this.setHandler(callbackHandler);
	}
	
	@SuppressWarnings("unchecked")
	public boolean login() throws LoginException, javax.security.auth.login.LoginException {
		// check, its already logged?
		if(this.getUserFromFramework()!=null) {
			try {
				User tmp = (User) getUserFromFramework();
				Exception e = new LoginUserAlreadyLoggedException(bundle.getMessage(this.getClass(), "userAlreadyLogged",
						ResourceBundleServiceProviderType.ERROR, tmp.getLogin()));
				javax.security.auth.login.LoginException securityException =
					new javax.security.auth.login.LoginException(e.getMessage());
				securityException.initCause(e);
				throw securityException;
			} catch (ResourceBundleNotFoundException e1) {
			} catch (MessageKeyNotFoundException e1) {
			}
		}
		if(this.getHandler() == null) {
			try {
				logger.error(bundle.getMessage(this.getClass(), "userLoginHandlerNull", ResourceBundleServiceProviderType.ERROR));
			} catch (ResourceBundleNotFoundException e) {
			} catch (MessageKeyNotFoundException e) {
			}
			
			return false;
		}
		
		OperationPersistenceServiceProvider op =
			OperationPersistenceServiceProviderFactoryImpl.getOperationPersistenceServiceProvider();
		SecurityServiceProvider security = 
			SecurityServiceProviderFactoryImpl.getSecurityServiceProvider();
		
		Callback name 		= security.createNameCallback("login");
		Callback password   = security.createPasswordCallback("password", false);
		try {
			this.getHandler().handle(name, password);
			this.setUser((User) op.call("LoginOperation",
					((net.pimpas.security.serviceprovider.auth.login.callback.name.NameCallback)name).getInputName(),
					((net.pimpas.security.serviceprovider.auth.login.callback.password.PasswordCallback)password).
					getPassword()));
			this.setOperation(OPERATION_LOGIN);
			return true;
		} catch (Throwable e) {
			javax.security.auth.login.LoginException securityException =
				new javax.security.auth.login.LoginException(e.getCause().getMessage());
			securityException.initCause(e.getCause());
			throw securityException;
		}
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public boolean commit() throws LoginException, GeneralSecurityException {
		if(super.getError() == Boolean.TRUE)
			return !super.getError();
		
		SecurityServiceProvider security = 
			SecurityServiceProviderFactoryImpl.getSecurityServiceProvider();
		
		if(this.getOperation() == null) {
			// TODO exception, log it!
		}
		
		if(this.getOperation().equals(OPERATION_LOGIN)) {
			Principal p = security.createPrincipal(LoginUtils.USER_SUBJECT, this.getUser());
			if(!this.getSubject().containsPrincipal(p))
				this.getSubject().addPrincipal(p);
			
			// save in context
			PimpasFramework.getCurrentInstance().getContext().getSession().setAttribute(LoginUtils.USER_KEY,
					this.getSubject());
			
			try {
				logger.debug(bundle.getMessage(this.getClass(), ResourceBundleServiceProviderType.LOG, "userLogged", new Locale("en", "US"), this.getUser().getLogin()));
			} catch (ResourceBundleNotFoundException e) {
			} catch (MessageKeyNotFoundException e) {
			}
			
			return Boolean.TRUE;
		}
		else if(this.getOperation().equals(OPERATION_LOGOFF)) {
			
		} else {
			// TODO exception, log it!
		}
		
		this.setOperation(null);
		return Boolean.FALSE;
	}

	@SuppressWarnings("unchecked")
	public boolean logout() throws LoginException, GeneralSecurityException {
		User user = (User) this.getUserFromFramework();
		if(user == null) {
			try {
				Exception e = new LoginUserNotLoggedInException(bundle.getMessage(this.getClass(), "userNotLogged",
						ResourceBundleServiceProviderType.ERROR));
				javax.security.auth.login.LoginException securityException =
					new javax.security.auth.login.LoginException(e.getMessage());
				securityException.initCause(e);
				throw securityException;
			} catch (ResourceBundleNotFoundException e1) {
			} catch (MessageKeyNotFoundException e1) {
			}
		}
		
		// remove
		FrameworkSession session = PimpasFramework.getCurrentInstance().getContext().getSession();
		Subject subject = (Subject) session.removeAttribute(LoginUtils.USER_KEY);
		Principal p = subject.getPrincipal(LoginUtils.USER_SUBJECT);
		subject.removePrincipal(p);
		
		try {
			logger.debug(bundle.getMessage(this.getClass(), ResourceBundleServiceProviderType.LOG, "userLogout",
					new Locale("en", "US"), ((User)p.getObject()).getLogin()));
		} catch (ResourceBundleNotFoundException e) {
		} catch (MessageKeyNotFoundException e) {
		}
		
		this.setOperation(OPERATION_LOGOFF);
		return true;
	}
	
	private Subject getSubjectFromFramework( ) {
		return (Subject) PimpasFramework.getCurrentInstance().getContext().getSession().getAttribute(LoginUtils.USER_KEY);
	}
	
	private User getUserFromFramework( ) {
		Subject sub = this.getSubjectFromFramework();
		if(sub == null) return null;
		
		return (User) ((Principal)sub.getPrincipal(LoginUtils.USER_SUBJECT)).getObject();
	}

	public boolean isDebugMode() {
		return debugMode;
	}

	public void setDebugMode(boolean debugMode) {
		this.debugMode = debugMode;
	}

	public CallbackHandler getHandler() {
		return handler;
	}

	public void setHandler(CallbackHandler handler) {
		this.handler = handler;
	}

	public Subject getSubject() {
		return subject;
	}

	public void setSubject(Subject subject) {
		this.subject = subject;
	}

	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}

	public Integer getOperation() {
		return operation;
	}

	public void setOperation(Integer operation) {
		this.operation = operation;
	}
}